package com.dataexa.ky.core.utils;

import cn.hutool.core.lang.Assert;
import lombok.SneakyThrows;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * @Author: Michael
 * @Date: 2020/12/29 4:47 下午
 */
public class DateUtil {

    /**
     * 默认 zoneId
     */
    private static final ZoneId DEFAULT_ZONE_ID = ZoneId.of("Asia/Shanghai");
    private static final ZoneOffset DEFAULT_ZONE_OFFSET = DEFAULT_ZONE_ID.getRules()
            .getOffset(Instant.now());

    /**
     * 时间格式（yyyy-MM-dd）
     */
    public static final String DATE_PATTERN = "yyyy-MM-dd";
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_PATTERN);
    /**
     * 时间格式（yyyy-MM-dd HH:mm:ss）
     */
    public static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);

    private static final Map<String, DateTimeFormatter> DATE_TIME_FORMATTER_MAP = new HashMap<>(2);

    static {
        DATE_TIME_FORMATTER_MAP.put(DATE_PATTERN, DATE_FORMATTER);
        DATE_TIME_FORMATTER_MAP.put(DATE_TIME_PATTERN, DATE_TIME_FORMATTER);
    }

    /**
     * 获取当天的开始时间
     *
     * @return
     */
    public static LocalDateTime getStartTime() {
        // 获取当天的零点
        LocalDateTime startToday = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
        return startToday;
    }


    /**
     * 获取当前的结束时间
     *
     * @return
     */
    public static LocalDateTime getEndTime() {
        // 获取当天结束时间
        LocalDateTime endToday = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
        return endToday;
    }

    /**
     * 计算两个日期相差的秒数
     *
     * @param startDateTime
     * @param endDateTime
     *
     * @return
     */
    public static long calLastedTime(
            LocalDateTime startDateTime,
            LocalDateTime endDateTime
    ) {
        Duration duration = Duration.between(startDateTime, endDateTime);
        return duration.toMillis() / 1000;
    }

    /**
     * 从全局缓存中拿 pattern 对应的 formatter 或者新建
     *
     * @param pattern pattern
     *
     * @return pattern 对应的 formatter
     */
    private static DateTimeFormatter getFormatter(String pattern) {
        return DATE_TIME_FORMATTER_MAP.getOrDefault(pattern, DateTimeFormatter.ofPattern(pattern));
    }

    /**
     * LocalDate 类型的日期格式化为指定格式的字符串
     *
     * @param localDate LocalDate 类型的日期
     * @param pattern   格式，如：DateUtils.DATE_PATTERN
     *
     * @return 返回指定格式字符串时间
     */
    public static String formatLocalDate(
            LocalDate localDate,
            String pattern
    ) {
        Assert.notNull(localDate, "传入的日期不可以为 [null]");
        return formatLocalDateTime(toLocalDateTime(localDate), pattern);
    }

    /**
     * LocalDateTime 类型的时间格式化为指定格式的字符串
     *
     * @param localDateTime LocalDateTime 类型的时间
     * @param pattern       格式，如 DateUtils.DATE_PATTERN
     *
     * @return 指定格式字符串时间
     */
    public static String formatLocalDateTime(
            LocalDateTime localDateTime,
            String pattern
    ) {
        Assert.notNull(localDateTime, "传入的日期不可以为 [null]");
        return localDateTime.format(getFormatter(pattern));
    }

    /**
     * 字符串转成 LocalDate 类型的日期 默认 str 形如 "yyyy-MM-dd"
     *
     * @param str 字符串日期
     *
     * @return LocalDate 类型的日期
     */
    public static LocalDate toLocalDate(String str) {
        return toLocalDate(str, DATE_PATTERN);
    }

    /**
     * 字符串转成 LocalDate 类型的日期
     *
     * @param str     字符串日期
     * @param pattern 字符串格式，如 DateUtils.DATE_PATTERN
     *
     * @return LocalDate 类型的日期
     */
    public static LocalDate toLocalDate(
            String str,
            String pattern
    ) {
        return LocalDate.parse(str, getFormatter(pattern));
    }

    /**
     * Date 类型日期转成 LocalDate 类型的日期
     *
     * @param date Date 类型的日期
     *
     * @return LocalDate 类型的日期
     */
    public static LocalDate toLocalDate(Date date) {
        Assert.notNull(date, "传入的日期不可以为 [null]");
        return toZonedDateTime(date).toLocalDate();
    }

    /**
     * 字符串类型的时间转成 LocalDateTime 类型的时间，默认形如 "yyyy-MM-dd HH:mm:ss"
     *
     * @param str 字符串时间，默认形如 "yyyy-MM-dd HH:mm:ss"
     *
     * @return LocalDateTime 类型的时间
     */
    public static LocalDateTime toLocalDateTime(String str) {
        return toLocalDateTime(str, DATE_TIME_PATTERN);
    }

    /**
     * 字符串类型的时间转成 LocalDateTime 类型的时间
     *
     * @param str     字符串时间
     * @param pattern 字符串时间格式
     *
     * @return LocalDateTime 类型的时间
     */
    public static LocalDateTime toLocalDateTime(
            String str,
            String pattern
    ) {
        return LocalDateTime.parse(str, getFormatter(pattern));
    }


    /**
     * Date 类型的时间转成 LocalDateTime 类型的时间
     *
     * @param date Date 类型的时间
     *
     * @return LocalDateTime 类型的时间
     */
    public static LocalDateTime toLocalDateTime(Date date) {
        Assert.notNull(date, "传入的日期不可以为 [null]");
        return toZonedDateTime(date).toLocalDateTime();
    }

    /**
     * LocalDate 时间转成 LocalDateTime 类型时间为当天开始时间
     *
     * @param localDate LocalDate 类型的时间
     *
     * @return LocalDateTime 类型时间为当天开始时间
     */
    public static LocalDateTime toLocalDateTime(LocalDate localDate) {
        Assert.notNull(localDate, "传入的日期不可以为 [null]");
        return localDate.atStartOfDay();
    }


    public static String getStringDate() {
        return LocalDateTime.now()
                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }


    /**
     * 从 Date 获取特定时区的时间
     *
     * @param date Date 类型的时间
     *
     * @return DateUtils.DEFAULT_ZONE_ID 标定的时区时间
     */
    public static ZonedDateTime toZonedDateTime(Date date) {
        Assert.notNull(date, "传入的日期不可以为 [null]");
        return date.toInstant()
                .atZone(DEFAULT_ZONE_ID);
    }

    /**
     * 从秒数拿到 LocalDateTime
     *
     * @param seconds 秒数
     *
     * @return localDateTime
     */
    public static LocalDateTime fromSeconds(long seconds) {
        return LocalDateTime.ofEpochSecond(seconds, 0, DEFAULT_ZONE_OFFSET);
    }

    /**
     * 从毫秒数拿到 LocalDateTime
     *
     * @param millSeconds 毫秒数
     *
     * @return localDateTime
     */
    public static LocalDateTime fromMillSeconds(long millSeconds) {
        Instant instant = Instant.ofEpochMilli(millSeconds);
        return LocalDateTime.ofInstant(instant, DEFAULT_ZONE_ID);
    }

    /**
     * 从 LocalDateTime 拿到秒数
     *
     * @param localDateTime localDateTime
     *
     * @return 秒数
     */
    public static long getSeconds(LocalDateTime localDateTime) {
        Assert.notNull(localDateTime, "传入的日期不可以为 [null]");
        return getMillSeconds(localDateTime) / 1000;
    }

    /**
     * 从 LocalDateTime 拿到毫秒数
     *
     * @param localDateTime localDateTime
     *
     * @return 毫秒数
     */
    public static long getMillSeconds(LocalDateTime localDateTime) {
        Assert.notNull(localDateTime, "传入的日期不可以为 [null]");
        return localDateTime.toInstant(DEFAULT_ZONE_OFFSET)
                .toEpochMilli();
    }

    /**
     * 获取两个日期之间的范围
     *
     * @param startDateStr
     * @param endDateStr
     *
     * @return
     */
    public static List<String> generateDateRange(
            String startDateStr,
            String endDateStr
    ) {
        List<String> dateRangeList = new ArrayList<>();

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        LocalDate startDate = LocalDate.parse(startDateStr, formatter);
        LocalDate endDate = LocalDate.parse(endDateStr, formatter);

        while (!startDate.isAfter(endDate)) {
            dateRangeList.add(startDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")));
            startDate = startDate.plusDays(1);
        }

        return dateRangeList;
    }

    /**
     * 根据一个日期，得出该日期的最小时间和最大时间
     * 如：20231222 ，输出["2023-12-22 00:00:00" , "2023-12-22 23:59:59"]
     * @param dateStr
     * @return
     */
    @SneakyThrows
    public static String[] getMinAndMaxTime(String dateStr) {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
            Date date = sdf.parse(dateStr);

            Calendar calendarStart = Calendar.getInstance();
            calendarStart.setTime(date);
            calendarStart.set(Calendar.HOUR_OF_DAY, 0);
            calendarStart.set(Calendar.MINUTE, 0);
            calendarStart.set(Calendar.SECOND, 0);

            Calendar calendarEnd = Calendar.getInstance();
            calendarEnd.setTime(date);
            calendarEnd.set(Calendar.HOUR_OF_DAY, 23);
            calendarEnd.set(Calendar.MINUTE, 59);
            calendarEnd.set(Calendar.SECOND, 59);

            SimpleDateFormat resultFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String startDateStr = resultFormat.format(calendarStart.getTime());
            String endDateStr = resultFormat.format(calendarEnd.getTime());
            return new String[]{startDateStr, endDateStr};
        } catch (ParseException e) {
            e.printStackTrace();
            throw e;
        }
    }
}
